
package com.feib.soeasy.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.List;

import javax.annotation.Resource;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.feib.soeasy.dao.HibernateDao;
import com.feib.soeasy.pagination.PaginatedList;

/**
 * @title (#)BaseHibernateDao.java<br>
 * @description Dao層的基礎類別<br>
 * @author Jimmy Liu<br>
 * @version 1.0.0 2010/11/25
 * @copyright Far Eastern International Bank Copyright (c) 2010<br>
 * @20101025 create by Jimmy Liu<br>
 */
public class BaseHibernateDao<T, PK extends Serializable> extends HibernateDaoSupport implements HibernateDao<T, PK> {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    private Class<T> persistentClass;

    @SuppressWarnings("unchecked")
    public BaseHibernateDao() {
        Type type = getClass().getGenericSuperclass();
        do {
            if (type != null && type instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType) type;
                if (paramType.getRawType().equals(BaseHibernateDao.class)) {
                    persistentClass = (Class<T>) paramType.getActualTypeArguments()[0];
                    type = null;
                }
            }
            if (type instanceof Class<?>) {
                type = ((Class<?>) type).getGenericSuperclass();
            }
            else {
                type = null;
            }
        }
        while (type != null);
    }

    @Resource(name = "sessionFactory")
    public void setMySessionFactory(SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public T findByPk(PK pk) {
        return findByPk(pk, false);
    }

    public T findByPk(PK pk, boolean lock) {
        T entity;
        if (lock)
            entity = (T) getHibernateTemplate().get(getPersistentClass(), pk, LockMode.PESSIMISTIC_WRITE);
        else
            entity = (T) getHibernateTemplate().get(getPersistentClass(), pk);

        return entity;
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return getHibernateTemplate().executeFind(new HibernateCallback()
        {

            public List doInHibernate(Session session)
            throws HibernateException, SQLException
            {
                Criteria crit = session.createCriteria(getPersistentClass());
                return crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .list();
            }
        });
    }

    public List<T> findByExample(T exampleInstance) {
        return findByExample(exampleInstance, true, true, true);
    }

    public List<T> findByExample(T exampleInstance, String... excludeProperties) {
        return findByExample(exampleInstance, true, true, true,
                        excludeProperties);
    }

    public List<T> findByExample(T exampleInstance, boolean ignoreCase,
                    boolean enableLike, boolean excludeZeroes,
                    String... excludeProperties) {
        Example e = Example.create(exampleInstance);
        if (ignoreCase) {
            e.ignoreCase();
        }
        if (enableLike) {
            e.enableLike(MatchMode.START);
        }
        if (excludeZeroes) {
            e.excludeZeroes();
        }

        if (excludeProperties != null) {
            for (String excludeProperty : excludeProperties) {
                e.excludeProperty(excludeProperty);
            }
        }

        return findByCriteria(e);
    }

    public T save(final T entity) {
        getHibernateTemplate().saveOrUpdate(entity);
        return entity;
    }

    public void delete(final T entity) {
        getHibernateTemplate().delete(entity);
    }

    public void deleteById(PK pk) {
        delete(findByPk(pk, false));
    }

    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(final Criterion... criterions) {
        return getHibernateTemplate().executeFind(new HibernateCallback()
        {

            public List doInHibernate(Session session)
            throws HibernateException, SQLException
            {
                Criteria crit = session.createCriteria(getPersistentClass());
                for (Criterion criterion : criterions)
                {
                    crit.add(criterion);
                }
                return crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .list();
            }
        });

    }

    @SuppressWarnings("unchecked")
    protected List<T> findByQuery(String query, Object... parameters) {
        if (parameters == null) {
            return getHibernateTemplate().find(query);
        }
        else {
            return getHibernateTemplate().find(query, parameters);
        }
    }

    protected DetachedCriteria queryDetachedCriteriaByEnity(T entity)
    {
        DetachedCriteria criteria = DetachedCriteria.forClass(this.getPersistentClass());
        criteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY);

        Example example = Example.create(entity);
        example.enableLike(MatchMode.START);
        example.excludeZeroes();
        criteria.add(example);

        return criteria;
    }

    /**
     * 依查詢條件與分頁設定查詢資料庫，並回傳設定的筆數
     * @param entity 查詢條件
     * @param paginatedList 每頁筆數與取出第幾頁設定
     * @return 查詢結果
     */
    @SuppressWarnings("unchecked")
    protected PaginatedList<T> queryPaginationByEnity(T entity, PaginatedList<T> paginationConfig)
    {
        PaginatedList<T> result = new PaginatedList<T>();
        result.setPageSize(paginationConfig.getPageSize());
        result.setPageNumber(paginationConfig.getPageNumber());
        result.setSortCriterion(paginationConfig.getSortCriterion());
        result.setSortDirection(paginationConfig.getSortDirection());

        result.setFullListSize(countByEnity(entity));


        // 筆數大於0
        if (0 < result.getFullListSize())
        {
            DetachedCriteria criteria = queryDetachedCriteriaByEnity(entity);

            //排序
            if (null != paginationConfig.getSortCriterion())
            {
                // 由小到大
                if (PaginatedList.SORT_DIRECTION_ASC == paginationConfig.getSortDirection())
                {
                    criteria.addOrder(Order.asc(paginationConfig.getSortCriterion()));
                }
                else // 由大到小
                {
                    criteria.addOrder(Order.asc(paginationConfig.getSortCriterion()));
                }
            }


            int maxResults = result.getPageSize(); //the maximum number of results
            int firstResult = result.getFirstIndexForCurrentPage();  //the first result to retrieve, numbered from 0
            result.setPaginatedList(this.getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults));
        }

        return result;
    }

    /**
     * 依查詢條件查到的總筆數
     * @param entity 查詢條件
     * @return 依查詢條件查到的總筆數
     */
    protected int countByEnity(T entity)
    {
        DetachedCriteria criteria = queryDetachedCriteriaByEnity(entity);
        return countByDetachedCriteria(criteria);
    }

    protected int countByDetachedCriteria(DetachedCriteria criteria)
    {
        criteria.setProjection(Projections.rowCount());
        return ((Number)this.getHibernateTemplate().findByCriteria(criteria).iterator().next()).intValue();
    }


}
